#ifndef PARTICLE_H
#define PARTICLE_H

#include "ParticleEnviron.h"
#include "ParticleSubEmitter.h"
#include "ParticleContainer.h"

class CParticleSubEmitter;
class CParticleEmitter;
struct SParticleRenderData;
struct SParticleVertexContext;

#define fMAX_COLLIDE_DEVIATION				0.1f

//////////////////////////////////////////////////////////////////////////
struct SParticleUpdateContext
{
	float						fUpdateTime;
	uint32					nEnvFlags;
	Vec3						vEmitBox;
	Vec3						vEmitScale;
	AABB*						pbbDynamicBounds;
	bool						bHasTarget;
	float						fMaxLinearDeviation;
	float						fMaxLinearStepTime;
	float						fMinStepTime;

	struct  
	{
		Vec3							vCenter, vSize;
		Vec3							vScaledAxes[3];
	} SpaceLoop;
} _ALIGN(16);

// dynamic particle data
// To do opt: subclass for geom particles.

#if !defined(PS3) && !defined(XENON)
	#define PARTICLE_EDITOR_FUNCTIONS
#endif

//////////////////////////////////////////////////////////////////////////
class CParticle : public CParticleSource
{
public:

  CParticle() 
  {
		ZeroStruct(*this);
  }
	CParticle(CParticleContainer* pCont)
	{
		ZeroStruct(*this);
		InitAllocations(pCont);
	}

	~CParticle();

	template<class S, class M> inline
	typename TVarEPParam<S>::T GetMaxValueMod( TVarEPParam<S> const& param, M mod ) const
	{
		typename TVarEPParam<S>::T val = mod;
		return val * param.GetMaxValue();
	}

	void InitAllocations( CParticleContainer* pCont );
	void Init( SParticleUpdateContext const& context, float fAge, CParticleSubEmitter* pEmitter, const EmitParticleData& data );
  void Update( SParticleUpdateContext const& context, float fUpdateTime );
	void Deactivate();
	void GetPhysicsState();

	void Transform( QuatTS const& qp )
	{
		m_Loc = qp * m_Loc;
		m_Vel.vLin = qp.q * m_Vel.vLin * qp.s;
		m_Vel.vRot = qp.q * m_Vel.vRot;
	}

	float GetAlpha() const
	{
		return GetParams().fAlpha.GetValueFromMod(m_BaseAlpha, GetRelativeAge());
	}

	ILINE uint16 GetEmitterSequence() const
	{
		return m_nEmitterSequence;
	}

#if PARTICLE_MOTION_BLUR
	void UpdatePrevState(const SVertexParticle& baseVert)
	{
		m_Prev.vPos = baseVert.xyz;
		m_Prev.vXAxis = baseVert.xaxis;
		m_Prev.vYAxis = baseVert.yaxis;
		m_Prev.bValid = true;
	}
#endif

	void UpdateBounds( AABB& bb, SParticleMoveState const& state ) const;
	void UpdateBounds( AABB& bb ) const
	{
		UpdateBounds( bb, *this );
	}

	// Rendering functions.
	void GetRenderLocation( PivotLocation& loc ) const;
	bool RenderGeometry( SRendParams& RenParamsShared, SParticleVertexContext& context ) const;
	void AddLight() const;
	int SetVertices( SVertexParticle aVerts[], SParticleVertexContext& context ) const;
#ifdef PARTICLE_EDITOR_FUNCTIONS
	bool NeedReallocation() const;
#endif
	static size_t GetAllocationSize(const CParticleContainer* pCont);
	void GetMemoryUsage( ICrySizer *pSizer ) const { /*nothing*/ }

private:

	//////////////////////////////////////////////////////////////////////////
	// For particles with tail, keeps history of previous locations.
	struct SParticleHistory
	{
		QuatTS	Loc;
		float		fAge;

		bool IsUsed() const		{ return fAge >= 0.f; }
		void SetUnused()			{ fAge = -1.f; }
	} _ALIGN(16);

	// Track sliding state.
	struct SSlideInfo
	{
		_smart_ptr<IPhysicalEntity>
									pEntity;			// Physical entity hit.
		Vec3					vNormal;			// Normal of sliding surface.
		float					fFriction;		// Sliding friction, proportional to normal force.
		float					fSlidingTime;	// Cumulative amount of time sliding.

		SSlideInfo()
			{ Clear(); }
		void Clear()
			{ ClearSliding(Vec3(ZERO)); }
		void ClearSliding( const Vec3& vNormal_ )
		{ 
			pEntity = 0;
			fFriction = 0;
			fSlidingTime = -1.f;
			vNormal = vNormal_; 
		}
		void SetSliding( IPhysicalEntity* pEntity_, const Vec3& vNormal_, float fFriction_ )
		{ 
			pEntity = pEntity_;
			vNormal = vNormal_;
			fFriction = fFriction_;
			fSlidingTime = 0.f;
		}
		bool IsSliding() const
			{ return fSlidingTime >= 0.f; }
	};

	// Track predicted collisions.
	struct SHitInfo
	{
		Vec3				vPos;							// Hit position.
		Vec3				vPathDir;					// Direction of reverse path.
		float				fPathLength;			// Length of reverse path.
		Vec3				vNormal;					// Normal of hit surface.
		_smart_ptr<IPhysicalEntity>
								pEntity;					// Physical entity hit.
		int					nSurfaceIdx;			// Surface index of hit; -1 if no hit.

		SHitInfo()
			{ Clear(); }
		void Clear()
			{ nSurfaceIdx = -1; fPathLength = 0.f; }

		bool HasPath() const
			{ return fPathLength > 0.f; }
		bool HasHit() const
			{ return nSurfaceIdx >= 0; }
		void SetMiss( const Vec3& vStart_, const Vec3& vEnd_ )
			{ SetHit( vStart_, vEnd_, Vec3(0.f) ); }
		void SetHit( const Vec3& vStart_, const Vec3& vEnd_, const Vec3& vNormal_, int nSurfaceIdx_ = -1, IPhysicalEntity* pEntity_ = NULL )
		{
			vPos = vEnd_;
			vPathDir = vStart_ - vEnd_;
			fPathLength = vPathDir.GetLength();
			if (fPathLength > 0.f)
				vPathDir /= fPathLength;
			vNormal = vNormal_;
			nSurfaceIdx = nSurfaceIdx_;
			pEntity = pEntity_;
		}

		// If path invalid, returns false.
		// If path valid returns true; if hit.dist < 1, then hit was matched.
		bool TestHit( ray_hit& hit, const Vec3& vPos0, const Vec3& vPos1, const Vec3& vVel0, const Vec3& vVel1, float fMaxDev, float fRadius = 0.f ) const;
	};

	struct SCollisionInfo
	{
		SSlideInfo	Sliding;
		SHitInfo		Hit;

		void Clear()
		{ 
			Sliding.Clear(); 
			Hit.Clear();
		}
	};

  // Current state/param values, needed for updating.
	float		m_fAngle;

#if PARTICLE_MOTION_BLUR
  // Previous state. Used for motion blur
	struct SBlurState
	{
		Vec3 vPos;
		Vec3 vXAxis;
		Vec3 vYAxis;
		bool bValid;
	} m_Prev;
#endif

	SParticleHistory*			m_aPosHistory;						// History of positions, for tail. Allocated and maintained by particle.
	SCollisionInfo*				m_pCollisionInfo;					// Predicted collision info.

	uint8									m_nCollisionCount;				// Number of collisions this particle has had. Max value = resting.

	// Constant values.
	uint8									m_nTileVariant;						// Selects texture tile.
	uint16								m_nEmitterSequence;				// Which sequence particle is part of (for connected rendering).

	// Base modifications (random variation, emitter strength) for this particle of variable parameters.
	// Stored as compressed fraction from 0..1.
	struct SBaseMods
	{
		// Random modifiers for unsigned params.
		TFixed<uint8,1>
								Size,
								TailLength,
								Stretch,

								AirResistance,
								Turbulence3DSpeed,
								TurbulenceSize,

								LightSourceIntensity,
								LightSourceRadius;

		// Random modifiers for signed params.
		TFixed<int8,1>
								GravityScale,
								TurbulenceSpeed,
								OrbitDistance;

		void Init()
		{
			memset(this, 0xFF, sizeof(*this));
		}
	}											m_BaseMods;

	Color3B					m_BaseColor;
	UnitFloat8 			m_BaseAlpha;

	// External references.
	CParticleContainer*							m_pContainer;			// Container particle lives in.

	// Functions.
private:
	SParticleMoveState& GetMoveState()							{ return static_cast<SParticleMoveState&>(*this); }
	CParticleContainer& GetContainer() const				{ return *m_pContainer; }
	CParticleEmitter& GetMain() const								{ return m_pContainer->GetMain(); }
	ResourceParticleParams const& GetParams() const	{ return GetContainer().GetParams(); }
	inline float GetBaseRadius() const							{ return GetParams().GetMaxObjectSize(m_EmitGeom.m_pStatObj); }
	float GetVisibleRadius() const									{ assert(m_Loc.s >= 0.f); return m_Loc.s * GetBaseRadius(); }
	float GetPhysicalRadius() const									{ return GetVisibleRadius() * GetParams().fThickness; }
	inline Vec3 GetNormal() const										{ return m_Loc.q.GetColumn1(); }

	void InitPos( SParticleUpdateContext const& context, QuatTS const& loc, float fEmissionRelAge );
	void AddPosHistory( SParticleMoveState const& stateNew );
	void UpdateRotation( SParticleMoveState& state, float fTime = 0.f ) const;
	Vec3 VortexRotation( SParticleMoveState& state, float fTime = 0.f ) const;
	void TargetMovement( SParticleMoveState& state, float fTime, float fRelativeAge, ParticleTarget const& target ) const;
	float TravelSlide( SParticleMoveState& state, SSlideInfo& sliding, float fTime, const Vec3& vExtAccel, float fMaxSlide, float fMinStepTime ) const;
	void Move( SParticleMoveState& state, float fTime, SExternForces const& forces, ParticleTarget const& target ) const;
	float MoveLinear( SParticleMoveState& state, SCollisionInfo& coll, float fTime, SExternForces const& forces, ParticleTarget const& target, float fMaxLinearDev, float fMaxSlideDev, float fMinStepTime ) const;
	bool Collide( ray_hit& hit, float fTime, SParticleUpdateContext const& context, const SExternForces& forces, ParticleTarget const& target, const SParticleMoveState& stateNew, SCollisionInfo& collNew );

	void Physicalize();
	int GetSurfaceIndex() const;
	void GetCollisionParams(int nCollSurfaceIdx, float& fBounce, float& fDrag) const;

	void ComputeRenderData( SParticleRenderData& data, SParticleVertexContext& context, float fRelativeAge, float fObjectSize ) const;
	int FillTailVertBuffer( SVertexParticle aTailVerts[], SParticleVertexContext const& context ) const;

	void DebugBounds( SParticleMoveState const& state ) const;

} _ALIGN(128);

#endif // PARTICLE
